package org.example.backend.utils;

import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author 啊昌
 * @date 2025/7/21 16:26
 * FFmpegUtils 视频转码工具类
 */
@Component
@Slf4j
public class FfmpegUtils {
	private final Logger logger = LoggerFactory.getLogger(FfmpegUtils.class);
	// 默认转码参数
	private final String DEFAULT_CODEC = "libx264";
	private final String DEFAULT_PRESET = "medium";
	private final String DEFAULT_CRF = "23";
	private final String DEFAULT_AUDIO_CODEC = "aac";
	private final int DEFAULT_AUDIO_BITRATE = 128; // kbps
	private final int DEFAULT_THREADS = 2;


	/**
	 * 从输入流中读取数据并输出到控制台。
	 *
	 * @param inputStream 输入流
	 */
	private void readStream(InputStream inputStream) {
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
			String line;
			while ((line = reader.readLine()) != null) {
				System.out.println("[ffmpeg] " + line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 将给定的 MultipartFile 转换为 MP4 格式的 MultipartFile。
	 *
	 * @param sourceFile 待转换的源文件
	 * @return 转换后的 MP4 格式 MultipartFile
	 * @throws Exception 如果转换过程中发生异常
	 */
	public MultipartFile convertToMp4MultipartFile(MultipartFile sourceFile) throws Exception {
		// 1. 保存源文件为临时文件
		File inputFile = File.createTempFile("input_", getFileExtension(sourceFile.getOriginalFilename()));
		sourceFile.transferTo(inputFile);

		// 2. 目标 mp4 文件
		File outputFile = File.createTempFile("output_", ".mp4");

		// 3. 构造命令，使用 ProcessBuilder 更安全
		List<String> command = Arrays.asList(
				"ffmpeg",
				"-y", // 自动覆盖输出文件，避免等待确认
				"-i", inputFile.getAbsolutePath(),
				"-c:v", "libx264",
				"-preset", "fast",
				"-crf", "23",
				"-c:a", "aac",
				"-b:a", "128k",
				outputFile.getAbsolutePath()
		);

		ProcessBuilder builder = new ProcessBuilder(command);
		builder.redirectErrorStream(true); // 合并标准输出和错误输出
		Process process = builder.start();

		// 异步读取日志，防止缓冲阻塞
		new Thread(() -> readStream(process.getInputStream())).start();

		// 等待执行完成
		int exitCode = process.waitFor();

		if (exitCode != 0 || !outputFile.exists()) {
			throw new RuntimeException("FFmpeg 转码失败，exitCode=" + exitCode);
		}

		// 4. 构造 MultipartFile 实现类
		MultipartFile result = new MultipartFile() {
			@Override
			public String getName() {
				return "file";
			}

			@Override
			public String getOriginalFilename() {
				return sourceFile.getOriginalFilename().replaceAll("\\..*$", ".mp4");
			}

			@Override
			public String getContentType() {
				return "video/mp4";
			}

			@Override
			public boolean isEmpty() {
				return outputFile.length() == 0;
			}

			@Override
			public long getSize() {
				return outputFile.length();
			}

			@Override
			public byte[] getBytes() throws IOException {
				return java.nio.file.Files.readAllBytes(outputFile.toPath());
			}

			@Override
			public InputStream getInputStream() throws IOException {
				return new FileInputStream(outputFile);
			}

			@Override
			public void transferTo(File dest) throws IOException {
				java.nio.file.Files.copy(outputFile.toPath(), dest.toPath());
			}
		};

		// 5. 删除临时输入文件（保留输出）
		inputFile.delete();

		return result;
	}

	/**
	 * 获取文件扩展名
	 *
	 * @param filename 文件名
	 * @return 文件扩展名，如果没有扩展名则返回空字符串
	 */
	private String getFileExtension(String filename) {
		if (filename != null && filename.contains(".")) {
			return filename.substring(filename.lastIndexOf("."));
		}
		return "";
	}

	/**
	 * 将视频转码为MP4格式
	 *
	 * @param inputPath  输入文件路径
	 * @param outputPath 输出文件路径
	 * @return 是否转码成功
	 */
	public boolean convertToMp4(String inputPath, String outputPath) {
		return convertToMp4(inputPath, outputPath, DEFAULT_CODEC, DEFAULT_PRESET, DEFAULT_CRF, DEFAULT_AUDIO_CODEC, DEFAULT_AUDIO_BITRATE, DEFAULT_THREADS);
	}

	/**
	 * 自定义参数的视频转码
	 *
	 * @param inputPath    输入文件路径
	 * @param outputPath   输出文件路径
	 * @param videoCodec   视频编码器 (默认: libx264)
	 * @param preset       编码速度预设 (ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow)
	 * @param crf          质量系数 (0-51, 值越小质量越高)
	 * @param audioCodec   音频编码器
	 * @param audioBitrate 音频比特率 (kbps)
	 * @param threads      使用的线程数
	 * @return 是否转码成功
	 */
	public boolean convertToMp4(String inputPath, String outputPath, String videoCodec, String preset, String crf, String audioCodec, int audioBitrate, int threads) {

		// 验证输入文件存在
		File inputFile = new File(inputPath);
		if (!inputFile.exists()) {
			logger.error("输入文件不存在: {}", inputPath);
			return false;
		}

		// 准备输出目录
		File outputFile = new File(outputPath);
		File outputDir = outputFile.getParentFile();
		if (outputDir != null && !outputDir.exists() && !outputDir.mkdirs()) {
			logger.error("无法创建输出目录: {}", outputDir.getAbsolutePath());
			return false;
		}

		// 如果输出文件已存在，先删除它
		if (outputFile.exists()) {
			if (!outputFile.delete()) {
				logger.warn("无法删除已存在的输出文件: {}", outputPath);
				// 继续执行，因为FFmpeg的-y参数可能仍能覆盖
			}
		}

		// 构建FFmpeg命令
		List<String> command = new ArrayList<>();
		command.add("ffmpeg");
		command.add("-i");
		command.add(inputPath);
		command.add("-c:v");
		command.add(videoCodec);
		command.add("-preset");
		command.add(preset);
		command.add("-crf");
		command.add(crf);
		command.add("-c:a");
		command.add(audioCodec);
		command.add("-b:a");
		command.add(audioBitrate + "k");
		command.add("-threads");
		command.add(String.valueOf(threads));
		command.add("-y"); // 覆盖输出文件
		command.add("-movflags");
		command.add("+faststart"); // 流式播放优化
		command.add(outputPath);

		return executeFfmpegCommand(command);
	}

	/**
	 * 执行FFmpeg命令
	 *
	 * @param command 命令列表
	 * @return 是否执行成功
	 */
	private boolean executeFfmpegCommand(List<String> command) {
		ProcessBuilder processBuilder = new ProcessBuilder(command);
		processBuilder.redirectErrorStream(true); // 合并错误流到标准输出

		Process process = null;
		try {
			// 记录执行的命令
			logger.info("执行FFmpeg命令: {}", String.join(" ", command));

			long startTime = System.currentTimeMillis();
			process = processBuilder.start();

			// 读取输出流 (防止缓冲区填满导致进程阻塞)
			try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {

				String line;
				while ((line = reader.readLine()) != null) {
					logger.debug("FFmpeg: {}", line);
				}
			}

			// 等待进程结束
			boolean finished = process.waitFor(60, TimeUnit.MINUTES);
			long duration = System.currentTimeMillis() - startTime;

			if (!finished) {
				logger.error("FFmpeg执行超时 ({}分钟)", 60);
				process.destroyForcibly(); // 强制终止进程
				return false;
			}

			int exitCode = process.exitValue();
			if (exitCode == 0) {
				logger.info("FFmpeg转码成功! 耗时: {}秒", String.format("%.2f", duration / 1000.0));
				return true;
			} else {
				logger.error("FFmpeg转码失败! 退出码: {}, 耗时: {}秒", exitCode, String.format("%.2f", duration / 1000.0));
				return false;
			}
		} catch (IOException | InterruptedException e) {
			logger.error("FFmpeg执行异常: {}", e.getMessage(), e);
			return false;
		} finally {
			if (process != null) {
				try {
					// 确保进程和相关流被完全关闭
					process.getInputStream().close();
					process.getErrorStream().close();
					process.getOutputStream().close();
				} catch (IOException e) {
					logger.warn("关闭进程流异常: {}", e.getMessage());
				}
				process.destroyForcibly(); // 确保进程被强制终止
			}
		}
	}

	/**
	 * 检查FFmpeg是否可用
	 *
	 * @return 是否安装并可执行
	 */
	public boolean isFfmpegAvailable() {
		Process process = null;
		try {
			process = new ProcessBuilder("ffmpeg", "-version").start();
			int exitCode = process.waitFor();
			return exitCode == 0;
		} catch (IOException | InterruptedException e) {
			logger.warn("FFmpeg检查失败: {}", e.getMessage());
			return false;
		} finally {
			if (process != null) {
				process.destroyForcibly(); // 确保进程被终止
			}
		}
	}

	/**
	 * 获取视频时长(秒)
	 *
	 * @param videoPath 视频路径
	 * @return 时长(秒), 失败返回-1
	 */
	public double getVideoDuration(String videoPath) {
		List<String> command = new ArrayList<>();
		command.add("ffprobe");
		command.add("-v");
		command.add("error");
		command.add("-show_entries");
		command.add("format=duration");
		command.add("-of");
		command.add("default=noprint_wrappers=1:nokey=1");
		command.add(videoPath);

		ProcessBuilder processBuilder = new ProcessBuilder(command);
		Process process = null;
		try {
			process = processBuilder.start();
			try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {

				String output = reader.readLine();
				if (output != null) {
					return Double.parseDouble(output);
				}
			}

			process.waitFor();
		} catch (IOException | InterruptedException | NumberFormatException e) {
			logger.error("获取视频时长失败: {}", e.getMessage());
		} finally {
			if (process != null) {
				process.destroyForcibly(); // 确保进程被终止
			}
		}
		return -1;
	}

	/**
	 * 生成视频缩略图
	 *
	 * @param videoPath     视频路径
	 * @param outputPath    缩略图输出路径
	 * @param timeInSeconds 截图时间点(秒)
	 * @return 是否成功
	 */
	public boolean generateThumbnail(String videoPath, String outputPath, double timeInSeconds) {
		List<String> command = new ArrayList<>();
		command.add("ffmpeg");
		command.add("-ss");
		command.add(String.valueOf(timeInSeconds));
		command.add("-i");
		command.add(videoPath);
		command.add("-vframes");
		command.add("1");
		command.add("-q:v");
		command.add("2");
		command.add("-y");
		command.add(outputPath);

		return executeFfmpegCommand(command);
	}
}
